package cn.fanyamin.util.collection;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * The Class RingBuffer.
 * 
 * @param <T>
 *            the generic type
 */
@SuppressWarnings("unchecked")
public class RingBuffer<T> implements Iterable<T>, Serializable {

    /**
     * serialVersionUID.
     */
    private static final long serialVersionUID = 5713960559776741665L;

    /** The values. */
    private final T[] values; // queue elements

    /** The capacity. */
    private final int capacity;

    /** The count. */
    private int count = 0; // number of elements on queue

    /** The first. */
    private int first = 0; // index of first element of queue

    /** The last. */
    private int last = 0; // index of next available slot

    // cast needed since no generic array creation in Java
    /**
     * Instantiates a new ring buffer.
     * 
     * @param capacity
     *            the capacity
     */
    public RingBuffer(int capacity) {
        this.capacity = capacity;
        values = (T[]) new Object[capacity];
    }

    /**
     * Checks if is empty.
     * 
     * @return true, if is empty
     */
    public boolean isEmpty() {
        return count == 0;
    }

    /**
     * Capacity.
     * 
     * @return the int
     */
    public int capacity() {
        return capacity;
    }

    /**
     * Size.
     * 
     * @return the int
     */
    public int size() {
        return count;
    }

    /**
     * {@inheritDoc}
     */
    public String toString() {
        StringBuilder sb = new StringBuilder("count=");
        sb.append(count);
        sb.append(" [");
        for (int i = first, j = 0; j < count; j++) {
            sb.append(values[i] + ";");
            i = (i + 1) % capacity;
        }
        sb.append("] ");
        return sb.toString();
    }

    /**
     * Insert.
     * 
     * @param idx
     *            the idx is the number begin from first
     * @param item
     *            the item
     */
    public void insert(int idx, T item) {
        if (idx < 0 || idx >= capacity) {
            throw new RuntimeException("Ring buffer overflow: " + idx);
        }

        while (count <= idx) {
            enqueue(null);
        }
        idx = (first + idx) % capacity;
        values[idx] = item;
    }

    /**
     * Clear.
     */
    public void clear() {
        while (!isEmpty()) {
            dequeue();
        }
    }

    /**
     * Gets the.
     * 
     * @param idx
     *            the idx
     * @return the t
     */
    public T get(int idx) {
        if (idx < 0 || idx >= capacity) {
            throw new RuntimeException("Ring buffer overflow: " + idx);
        }
        idx = (first + idx) % capacity;
        return values[idx];
    }

    /**
     * Gets the list.
     * 
     * @return the list
     */
    public List<T> getList() {
        List<T> retList = new ArrayList<T>(count);
        for (int i = first, j = 0; j < count; j++) {
            retList.add(values[i]);
            i = (i + 1) % capacity;
        }
        return retList;
    }

    /**
     * Enqueue.
     * 
     * @param item
     *            the item
     */
    public void enqueue(T item) {
        if (count == capacity) {
            throw new RuntimeException("Ring buffer overflow: " + count);
        }
        values[last] = item;
        last = (last + 1) % capacity; // wrap-around
        count++;
    }

    /**
     * Dequeue.
     * remove the least recently added item - doesn't check for underflow
     * 
     * @return the t
     */
    public T dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("Ring buffer underflow");
        }
        T item = values[first];
        values[first] = null; // to help with garbage collection
        count--;
        first = (first + 1) % capacity; // wrap-around
        return item;
    }

    /**
     * enqueue , if it's full, dequeue firstly.
     *
     * @param item  the item
     */
    public void add(T item) {
        if (count == capacity) {
            dequeue();
        }
        values[last] = item;
        last = (last + 1) % capacity; // wrap-around
        count++;
    }
    
    
    /**
     * {@inheritDoc}
     */
    public Iterator<T> iterator() {
        return new RingBufferIterator();
    }

    /**
     * The Class RingBufferIterator.
     * an iterator, doesn't implement remove() since it's optional
     */
    private class RingBufferIterator implements Iterator<T> {

        /** The i. */
        private int i = 0;

        /**
         * {@inherit}
         */
        public boolean hasNext() {
            return i < capacity;
        }

        /**
         * {@inherit}
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }

        /**
         * {@inherit}
         */
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return values[i++];
        }
    }

}